Skip to content

Reduce archived conversation disk usage with cold storage - #4016

Closed
Quicksaver wants to merge 101 commits into
pingdotgg:mainfrom
Quicksaver:split/conversation-data-savings
Closed

Reduce archived conversation disk usage with cold storage#4016
Quicksaver wants to merge 101 commits into
pingdotgg:mainfrom
Quicksaver:split/conversation-data-savings

Conversation

@Quicksaver

@Quicksaver Quicksaver commented Jul 15, 2026

Copy link
Copy Markdown

Summary

This adds compressed cold storage for archived conversations. Once a thread is archived, its conversation data and attachments are moved out of the hot state database into a dedicated archive.sqlite bundle, while unarchiving restores the complete thread tree before applying the domain command.

The lifecycle is durable and restart-safe: migrations 035/036 queue existing archived and deleted threads, background work rediscovers missed archive/delete jobs, and restoration keeps the cold bundle authoritative until the unarchive command commits successfully. Command receipts stay hot for retry idempotency until permanent deletion, while attachment ownership is preserved from exact persisted IDs and cold-bundle filenames throughout archive, restore, and durable deletion.

Archived thread shells remain consistent across clients without relying on compacted event history: each WebSocket session reconciles an authoritative shell snapshot, then replays live changes without losing archive removals during the handoff. Archive and deletion lifecycle events also close server preview sessions and clear renderer preview state so unarchive starts without stale mini-players, capture sessions, or desktop tabs.

What Changed

  • Added a dedicated cold-storage service that gzip-compresses thread-scoped SQL rows and attachments into chunked records in archive.sqlite.
  • Ran archive eligibility checks and provider/terminal/log-writer quiescing under the same per-tree lock used by restore, so delayed archive jobs cannot disrupt a thread that has already been unarchived.
  • Represented archive codec and bundle-validation failures with structured tagged errors while preserving genuine underlying causes at encoding/compression boundaries.
  • Required every cold-storage service error to retain its underlying failure cause.
  • Retained command receipts in hot storage while a conversation is archived so repeated command IDs preserve their accepted result.
  • Removed successfully archived rows, attachments, and per-thread provider logs from hot storage, with incremental vacuuming to reclaim database pages.
  • Restored archived thread trees transactionally before unarchive commands, with rollback on command failure and cleanup of the cold bundle only after a successful commit.
  • Treated restored manifests as reservations instead of replaying stale bundle rows and attachments over an already-active conversation.
  • Recovered restored reservations abandoned across process restarts while preserving process-local ownership for an unarchive that is still in flight.
  • Retried idempotent cold-bundle finalization when the same accepted unarchive command is dispatched again, without emitting a duplicate event or touching a mismatched thread.
  • Reserved still-hot archived rows before unarchive dispatch so queued lifecycle work cannot move them cold between the restore check and command commit, and rejected unarchive when storage cannot restore or reserve the conversation.
  • Scoped restored reservations to their original archive timestamp so a failed post-commit bundle finalization cannot block a later archive from moving the conversation cold.
  • Serialized archive-tree lifecycle operations with idle lock eviction, restored cleanup_pending bundles, and prevented queued archive work or post-commit failures from undoing a successful restore.
  • Added migrations 035/036 with persistent manifests, maintenance records, and delete queues so interrupted work and legacy archived/deleted threads are recovered after restart without replacing fork-local migration metadata.
  • Rediscovered soft-deleted shells during lifecycle recovery even when no cleanup-queue row was written before shutdown.
  • Rechecked archived shell state at the destructive transaction boundary and required active provider, terminal, and log writers to quiesce before archiving hot rows.
  • Changed thread deletion to permanently remove hot rows, cold bundles, attachments, provider logs, and related cleanup records.
  • Kept non-missing attachment/provider-log directory failures retryable instead of treating them as empty directories.
  • Matched attachment ownership by exact persisted IDs and cold-bundle filenames so colliding sanitized thread segments cannot remove another live thread's files.
  • Resumed cleanup_pending manifests even when the archived shell row is missing, while preserving exact attachment metadata until durable delete cleanup succeeds.
  • Added one-time compaction of legacy hot storage and startup processing for pending archive/delete work.
  • Closed per-thread provider log writers before lifecycle cleanup so files can be removed safely.
  • Closed server preview sessions when archive/delete events are observed and cleared renderer preview plus mini-player state after authoritative shell removal, releasing capture and desktop-tab leases before a later unarchive.
  • Preserved live-environment preview baselines while the environment catalog is temporarily unavailable so stale preview state still receives removal reconciliation.
  • Applied optimistic archive visibility consistently to rendered rows, project sorting, and keyboard navigation on web, and kept in-progress unarchive states across the web and mobile archive interfaces.
  • Kept the branch-owned Archive settings surface in a dedicated panel that groups archived snapshots across connected environments and exposes unarchive and permanent-delete actions.
  • Made Sidebar V2 project deletion apply archive-aware warnings and per-member deletion options across selected projects, so archived-only members are removed without bypassing live-thread safeguards.
  • Excluded optimistically archived threads from every web project-activity sort consumer and command-palette thread/project action so a hidden latest thread cannot reorder projects or remain a navigation target.
  • Sorted invalid or missing mobile archive timestamps after valid timestamps for both newest-first and oldest-first views.
  • Refreshed thread shells from an authoritative socket-owned snapshot on every completion-capable WebSocket session, with live buffering before the projection read so archived and deleted conversations cannot linger after event compaction.
  • Retried failed thread-detail cache evictions on later shell items while the thread remains absent, and revived tombstoned caches before persisting authoritative active detail snapshots after event compaction.
  • Added the supported t3-sqlite-state inspection path for both hot state.sqlite data and cold archive.sqlite manifests/chunks.
  • Added coverage for archive/restore round trips, binary SQL values, attachment handling, schema evolution, bounded restore reads, failure recovery, migrations, orchestration integration, and sidebar archive visibility.

Why

With fairly moderate usage, the user .t3 folder grew to more than 30 GB. That is far too much local data growth for normal use, and without lifecycle controls it gets out of hand very quickly. Archived and deleted conversations need to stop accumulating indefinitely in hot storage while remaining reliably recoverable when a user unarchives them.

Validation

  • pnpm exec vp check passed with exit 0; it reported 9 pre-existing lint warnings outside the changed files.
  • pnpm exec vp run typecheck passed with exit 0.
  • pnpm exec vp run lint:mobile passed with exit 0; optional SwiftLint, ktlint, and detekt checks were skipped because those tools are not installed locally.
  • Focused archive storage, deletion, orchestration, migrations, SQLite inspection, shell/detail-cache synchronization, sidebar/project deletion, mobile archive sorting, and preview lifecycle tests passed.
  • The restored-reservation recovery change passed 2 focused files/28 tests plus changed-file formatting, lint, and type-aware diagnostics; the full server typecheck is currently blocked by upstream HttpResponseCompression.ts (Response.body).
  • The locked archive-quiescing and structured validation-error changes passed 2 focused files/30 tests plus changed-file formatting and type-aware/type-check diagnostics; diagnostics reported one pre-existing string-spread warning.
  • Changed-file formatting and lint checks passed; affected server and client-runtime typechecks passed with only pre-existing Effect suggestions outside the changed files, while changed web files passed type-aware diagnostics.
  • Isolated Playwright runs verified archive removal from rendered and keyboard-addressable sidebar state, authoritative shell synchronization, Archive route loading, project ordering, and command-palette exclusion without console or page errors.
  • The iOS development client built, installed, and launched on an iPhone 17 Pro simulator. The archive-screen UI pass was blocked before application code by this worktree's Metro resolution of strict pnpm symlinks; focused mobile archive-list tests cover both sort directions.

Proof

No attached visual proof is necessary; the data lifecycle and sidebar interaction are covered by the automated and isolated Playwright validation above.

Note

Reduce archived conversation disk usage with cold storage archival and cache eviction

  • Introduces a server-side cold storage system (ThreadColdStorage) that archives thread data to a separate archive.sqlite database; the ThreadDeletionReactor is extended into a general lifecycle reactor that enqueues and processes archive, delete, and compaction jobs.
  • Adds DB migrations 035 and 036 to create thread_archive_manifests and thread_cleanup_queue tables and backfill existing archived/deleted threads into the new queues.
  • Thread unarchive in the orchestration engine now restores data from cold storage before processing, and rolls back the restoration if the command fails.
  • Client-side thread detail cache now evicts entries on archive and revives them on unarchive, with generation-aware persistence to prevent stale writes; archived threads are no longer persisted to the detail cache.
  • Adds an optimistic archive store so the sidebar, command palette, and project sorting immediately hide threads when archiving, before the server event arrives.
  • Bumps IndexedDB schema to version 5 and mobile SQLite schema to version 2, clearing previously cached thread snapshots on upgrade.
  • Risk: existing archived and deleted threads are enqueued for background cold storage migration on first server startup after migration 035/036; this process runs asynchronously but involves database writes proportional to thread history.

Macroscope summarized a7332c6.

- Preserve binary SQL values across archive round trips
- Keep cold bundles authoritative until attachments restore safely
- Bound restore memory and tolerate compatible schema changes
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6488407-a084-41a5-8fb2-a217af5d644e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 15, 2026
Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts Outdated
Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts
Comment thread apps/server/src/orchestration/Layers/OrchestrationEngine.ts
Comment thread apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts
@macroscopeapp

macroscopeapp Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

You can customize Macroscope's approvability policy. Learn more.

- Serialize archive-tree lifecycle and recheck archived shells
- Restore cleanup-pending bundles before unarchive commits
- Preserve retry state for writer and filesystem failures
Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One Effect service convention issue found in the new ThreadColdStorage service. See the inline comment.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/orchestration/Services/ThreadColdStorage.ts Outdated
- Reference-count archive-tree lock users and waiters
- Remove lock entries after the final operation releases them
- Define the service members inline with Context.Service
- Use the inferred Service type in the layer and orchestration test
Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts Outdated
Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts
- Match archived attachments by exact persisted ids
- Resume cleanup pending manifests without shell rows
- Preserve attachment metadata until durable delete cleanup succeeds
Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts
Comment thread apps/web/src/components/Sidebar.tsx
- Reuse archive filtering for project rows and navigation

- Cover persisted and optimistic archive visibility
- Treat persisted shell data as fast-paint cache only
- Resume WebSocket events from freshly loaded snapshots
- Describe cold archive, restoration, and deletion behavior
- Record sidebar consistency requirements and development ports
- Document authoritative shell refresh, event replay, and deferred cache writes
- Preserve mobile archive timestamp and shell subscription safeguards
Quicksaver and others added 7 commits July 27, 2026 16:35
- Describe event observation as scheduling lifecycle work
- Preserve best-effort preview shutdown semantics
- Drop renderer refs for removed environments
- Close archived previews before queued lifecycle work
- Cover catalog removal and delayed archive regressions
…data-savings

# Conflicts:
#	apps/web/src/browser/ElectronBrowserHost.tsx
Comment thread apps/web/src/browser/previewThreadLifecycle.ts
Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts
Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts
Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts
- Keep command receipts hot and rediscover missed deletions
- Avoid stale restore replays and retain live preview baselines
Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts
Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts
T3 Verification added 6 commits July 29, 2026 11:50
- Track live restore ownership to protect active unarchives
- Requeue abandoned restored manifests for cold storage
- Cover restart recovery and document lifecycle behavior
…data-savings

# Conflicts:
#	apps/server/src/provider/Layers/EventNdjsonLogger.ts
#	apps/web/src/components/settings/SettingsPanels.tsx
#	packages/client-runtime/src/rpc/client.ts

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect service conventions: two error-modeling findings in the new ThreadColdStorage implementation. The service tag/interface, layer wiring, and dependency acquisition otherwise look consistent with the conventions.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts Outdated
Comment thread apps/server/src/orchestration/Layers/ThreadColdStorage.ts Outdated
Comment thread apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts
T3 Verification added 2 commits July 29, 2026 22:24
- Run archive cleanup under the tree lock
- Structure archive validation failures

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a7332c6. Configure here.

if (refreshedReadModel !== null) {
commandReadModel = refreshedReadModel;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale read model after partial rollback

Medium Severity

When unarchive restore succeeds but the domain command fails, rollbackRestoreTree can still commit the destructive archive boundary (hot sessions, turns, and proposed plans deleted, manifest set to cleanup_pending) and then fail in completeArchiveCleanup. That failure makes rolledBack false, so the engine skips the getCommandReadModel refresh. Event reconcile also no-ops when no unarchive event was persisted, leaving commandReadModel with phantom plan, session, and latest-turn state for later commands in the same process.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a7332c6. Configure here.

{
discard: true,
},
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rollback archive skips quiescing

Medium Severity

rollbackRestoreTree re-archives through archiveImpl with an empty quiesce effect. After a failed unarchive of a still-hot archived shell, provider sessions, terminals, and log writers may still be active because the lifecycle archive job never ran, so rollback can delete hot rows and history while writers continue.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a7332c6. Configure here.

@juliusmarminge

Copy link
Copy Markdown
Member

Closing in favor of #2829 (orchestration V2).

#2829 deletes the V1 orchestration layer this PR builds on — apps/server/src/orchestration/**, provider/Layers/*Adapter.ts and provider/Services/** are removed and replaced by apps/server/src/orchestration-v2/**, with the IPC surface renamed to ORCHESTRATION_V2_WS_METHODS. The files this PR touches either no longer exist or are rewritten, so it can't be rebased — it would need reimplementing against the V2 adapters.

This is not a judgement on the change itself. Several of these are real gaps we still want fixed; the base just moved out from under them.

Once #2829 merges, please rebase onto main, port the change to the V2 equivalent, and reopen (or open a fresh PR). Ping me and I'll prioritise the review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants